﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace DDD.Core {

    /// <summary>
    /// 抽象化オブジェクトクラス
    /// </summary>
    /// <remarks>
    /// DDDで定義される全てのオブジェクトはこの<see cref="Object"/>型を継承します。
    /// <see cref="UserID"/>など全てのオブジェクトに共通する処理が実装されています。
    /// </remarks>
    public abstract class Object {

        #region Field
        int uniqueID;
        int userID;
        Dictionary<string, object> userObject;
        bool dirty;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 初期値は､
        /// <list type="bullet">
        ///   <item><see cref="UserID"/>=0</item>
        ///   <item><see cref="UserObject"/>=null</item>
        ///   <item><see cref="UniqueID"/>=自動発番</item>
        /// </list>
        /// です｡ユニークIDは自動的に一意な値がシステムによって割り振られます｡
        /// </remarks>
        public Object () {
            this.uniqueID = GetHashCode ();
            this.userID = 0;
            this.userObject = new Dictionary<string, object> ();
            this.dirty = true;
        }
        #endregion


        #region Property
        /// <summary>
        /// 汚染フラグ
        /// </summary>
        /// <remarks>
        /// 汚染フラグは一部のクラスで使われデータの更新が必要な事を示します。
        /// 典型的にはこのフラグがtrueの時はGPUにデータを転送する必要があります｡
        /// 通常ユーザーはこのプロパティを気にする必要はありません｡
        /// (メモ) こういう｢見えないフラグ｣はDDDの思想に反する｡
        /// </remarks>
        public bool IsDirty {
            get { return dirty; }
        }

        /// <summary>
        /// 一意なID
        /// </summary>
        /// <remarks>
        /// オブジェクトには自動的に一意なIDが発行されます。
        /// このIDはプログラム実行中にのみ有効です。ファイルには保存されません。
        /// ファイルに保存したオブジェクトをロードするとその都度異なるIDが発行されます。
        /// </remarks>
        public int UniqueID {
            get { return uniqueID; }
        }

        /// <summary>
        /// ユーザー定義のID
        /// </summary>
        /// <remarks>
        /// ユーザーはオブジェクトに任意のIDを付加可能です。
        /// このIDの使用方法はユーザーの自由です。
        /// このIDはファイルのセーブ・ロードで値が保存されます。
        /// </remarks>
        public int UserID {
            get { return userID; }
            set { this.userID = value; }
        }

        /// <summary>
        /// ユーザー定義の任意のオブジェクト
        /// </summary>
        /// <remarks>
        /// ユーザーはオブジェクトに任意のオブジェクトを付与可能です。
        /// このデータの使用方法はユーザーの自由です。
        /// このデータは(C#言語標準の)シリアライズ可能ならファイルのセーブ・ロードで値が保存されます。
        /// </remarks>
        public Dictionary<string, object> UserObject {
            get { return userObject; }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public virtual IEnumerable<Object> References {
            get {
                var objs = new List<Object> ();
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }



        #endregion

        #region Method

        /// <summary>
        /// 自分自身と自分から辿れる<see cref="Object"/>のうち指定のユーザーID(<see cref="UserID"/>)を持つものを探して返します
        /// </summary>
        /// <remarks>
        /// このオブジェクトとこのオブジェクトから到達できる全てのオブジェクトから指定のユーザーIDを持つものを１つ返します。
        /// 複数の<see cref="Object"/>が同一のユーザーIDを持つ場合、どれが帰るかは未定義です。
        /// </remarks>
        /// <param name="userID">ユーザーID</param>
        /// <returns>見つかればそのオブジェクト､そうでなければnull</returns>
        public Object Find (int userID) {
            return Finds (userID).ElementAtOrDefault(0);
        }


        /// <summary>
        /// 指定のユーザーID(<see cref="UserID"/>)の<see cref="Object"/>を全て探して返します
        /// </summary>
        /// <remarks>
        /// このオブジェクトとこのオブジェクトから到達できる全てのオブジェクトから指定のユーザーIDを持つものを全て返します。
        /// </remarks>
        /// <param name="userID">ユーザーID</param>
        /// <returns>オブジェクトの配列､見つからなかったときは空の配列</returns>
        public Object[] Finds (int userID) {
            var founds = new List<Object> ();
            if (this.userID == userID) {
                founds.Add(this);
            }
            foreach (var obj in References) {
                founds.AddRange (obj.Finds (userID));
            }
            return founds.ToArray();
        }

        /// <summary>
        /// 汚染フラグをセットします
        /// </summary>
        /// <remarks>
        /// このフラグをtrueにするとGPUに再度データが転送されます｡
        /// CPU側とGPU側のデータの同期は全て自動で行われるので､
        /// 通常ユーザーが汚染フラグを自分で変更する必要はありません｡
        /// </remarks>
        /// <param name="dirty">汚染フラグ</param>
        public void SetDirtyFlag (bool dirty) {
            this.dirty = dirty;
        }

        /// <summary>
        /// ユーザーオブジェクトをセットします
        /// </summary>
        /// <remarks>
        /// ユーザーは任意のオブジェクトを文字列をキーとして(いくつでも)登録可能です｡
        /// ユーザーオブジェクトの使い方はユーザーの任意です｡
        /// </remarks>
        /// <param name="name">名前</param>
        /// <param name="obj">オブジェクト</param>
        public void SetUserObject (string name, object obj) {
            if (name == null || name == "") {
                throw new ArgumentException ("Name is null or empty");
            }

            this.userObject[name] = obj;
        }

        /// <summary>
        /// 指定の名前のユーザーオブジェクトを取得します
        /// </summary>
        /// <remarks>
        /// 
        /// </remarks>
        /// <param name="name">名前</param>
        /// <returns>ユーザーオブジェクトまたはnull</returns>
        public object GetUserObject (string name) {
            if (name == null || name == "") {
                throw new ArgumentException ("Name is null or empty");
            }

            return userObject[name];
        }


        /// <summary>
        /// プロパティの名前を取得します
        /// </summary>
        /// <remarks>
        /// プロパティをプロパティの名前に変換します。
        /// 呼び出し例：<code>var name = Object.GetName({}=>x.SomeProperty)</code>
        /// </remarks>
        /// <typeparam name="T">式木の型</typeparam>
        /// <param name="e">式木</param>
        /// <returns>プロパティの名前</returns>
        public static string GetName<T> (Expression<Func<T>> e) {
            var member = (MemberExpression)e.Body;
            return member.Member.Name;
        }



        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("Object: ");
        }
        #endregion

    }
}
